#include <iostream>
#include <cerrno>
#include <cstring>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>

using namespace std;

#define PATHNAME "." // 项目名
#define PROJID 0x29C // 项目编号

enum
{
    SERVER = 0,
    CLIENT = 1
};

class shm
{
public:
    shm(int id)
        : _id(id)
    {
        _key = getKey();    //获取 key

        // 根据不同的身份，创建 / 打开 共享内存
        if (_id == SERVER)
            _shmid = shmHelper(_key, gsize, IPC_CREAT | IPC_EXCL | mode);
        else
            _shmid = shmHelper(_key, gsize, IPC_CREAT);

        // 关联共享内存
        _start = shmat(_shmid, NULL, 0); // 关联
        if (_start == (void *)-1)
        {
            cerr << "shmat fail!"
                 << "errno: " << errno << " | " << strerror(errno) << endl;
            exit(1);
        }
    }

    ~shm()
    {
        // 去关联
        int n = shmdt(_start);
        if (n == -1)
        {
            cerr << "shmdt fail!"
                 << "errno: " << errno << " | " << strerror(errno) << endl;
            exit(1);
        }

        // 根据不同的身份，判断是否需要删除共享内存
        if(_id == SERVER)
            shmctl(_shmid, IPC_RMID, NULL);
    }

    key_t getKey() const
    {
        key_t key = ftok(PATHNAME, PROJID);
        if (key == -1)
        {
            // 失败，终止进程
            cerr << "ftok fail!  "
                 << "errno: " << errno << " | " << strerror(errno) << endl;
            exit(1);
        }

        return key;
    }

    int getShmID() const
    {
        return _shmid;
    }

    void *getStart() const
    {
        return _start;
    }

protected:
    static const int gsize = 4096;
    static const mode_t mode = 0666;

    // 将十进制数转为十六进制数
    string toHEX(int x)
    {
        char buffer[64];
        snprintf(buffer, sizeof buffer, "0x%x", x);
        return buffer;
    }

    // 共享内存助手
    int shmHelper(key_t key, size_t size, int flags)
    {
        int shmid = shmget(key, size, flags);
        if (shmid == -1)
        {
            // 失败，终止进程
            cerr << "shmget fail!  "
                 << "errno: " << errno << " | " << strerror(errno) << endl;
            exit(2);
        }

        return shmid;
    }

private:
    key_t _key;
    int _shmid = 0;
    void *_start;
    int _id; // 身份标识符，用来区分服务端与客户端
};